package com.ly.chatsimple.tcp

import android.app.Application
import com.ly.chatsimple.tcp.task.ChatTask
import com.ly.chatsimple.tcp.task.RequestTask
import com.ly.chatsimple.tcp.task.TcpHeartBeatFactory
import com.ly.data.MessageProtobuf.Msg
import com.ly.data.MessageProtobuf.Req
import com.ly.data.MessageProtobuf.Resp
import com.ly.data.MessageProtobuf.TaskProto
import com.ly.nethub.NetHubFactory
import com.ly.nethub.bean.IPInfo
import com.ly.nethub.config.CODE_RESP_PARSE_FAILED
import com.ly.nethub.config.CODE_TASK_SEND_FAILED
import com.ly.nethub.config.CODE_TASK_SEND_SUCCESS
import com.ly.nethub.config.CODE_UNINITIALIZED
import com.ly.nethub.config.CommunicationProtocol
import com.ly.nethub.config.findMsg
import com.ly.nethub.config.netHubOptions
import com.ly.nethub.interf.INetHubInterface
import com.ly.nethub.interf.TaskSendListener
import com.ly.nethub.state.AppStateManager
import com.ly.nethub.task.BaseTask
import com.ly.nethub.utils.LogUtils
import io.netty.buffer.ByteBuf
import io.netty.channel.ChannelHandlerContext
import io.netty.handler.codec.LengthFieldBasedFrameDecoder
import io.netty.handler.codec.MessageToByteEncoder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeoutOrNull
import java.nio.ByteOrder
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException

object TcpClient {

    const val TAG = "TcpClient"

    @Suppress("MemberVisibilityCanBePrivate")
    var appContext: Application? = null
        private set

    var mClient: INetHubInterface? = null
        private set

    var ip: String = ""

    private val mHeartBeatFactory = TcpHeartBeatFactory()

    val emptyArray = byteArrayOf()

    fun init(context: Application, coroutineScope: CoroutineScope) {
        appContext = context
        AppStateManager.registerObserver(context, mHeartBeatFactory)
        mClient = NetHubFactory.createTcpNetHub(
            context = context,
            options = netHubOptions {
                val ips = listOf(IPInfo(ip = "10.55.0.31", port = 6666))
                setCommunicationProtocol(CommunicationProtocol.TCP(ips))
                setEncoders(listOf { TaskEncoder() })
                setDecoders(listOf { TaskDecoder() })
            }, heartbeatFactory = mHeartBeatFactory
        )
        coroutineScope.launch {
            mClient?.connect()
            ip = withTimeoutOrNull(10 * 1000L) {
                IPHelper.getLocalIPAddress()
            } ?: "com.ly.chatsimple"
        }
    }

    suspend fun reconnect() {
        mClient?.reconnect(false)
    }

    fun disconnect() {
        mClient?.disconnect()
    }

    fun release() {
        appContext?.let {
            AppStateManager.registerObserver(it, mHeartBeatFactory)
        }
        mClient?.release()
        mClient = null
    }

    /**
     * 请求
     * @param param proto数据类调用方法toByteArray得到
     * @param buildFunc 响应数据构造，proto数据类 **.parseFrom(byteArray)
     * @param ignoreRespNull 忽略Resp为空的情况
     */
    suspend inline fun <reified T> request(
        param: Req,
        ignoreRespNull: Boolean = false,
        crossinline buildFunc: (ByteArray) -> T?
    ): Response<T> {
        return try {
            val resp = executedTask<Resp>(RequestTask.create(param)) {
                Resp.parseFrom(it)
            }
            when {
                resp == null -> {
                    val code: Int = if (ignoreRespNull) {
                        CODE_TASK_SEND_SUCCESS
                    } else {
                        CODE_TASK_SEND_FAILED
                    }
                    Response(
                        data = null,
                        code = code,
                        msg = code.findMsg()
                    )
                }

                else -> {
                    val data = buildFunc(resp.data.toByteArray())
                    Response(
                        data = data,
                        msg = resp.msg,
                        code = resp.code
                    )
                }
            }

        } catch (e: BuildError) {
            Response(data = null, code = e.code, msg = e.message ?: "操作失败")
        } catch (e: Exception) {
            val code = CODE_TASK_SEND_FAILED
            Response(
                data = null,
                code = code,
                msg = code.findMsg()
            )
        }
    }

    suspend fun sendMsg(msg: Msg): Response<Any> {
        return try {
            executedTask(ChatTask.create(msg)) {}
            val code = CODE_TASK_SEND_SUCCESS
            Response(data = null, code = code, msg = code.findMsg())
        } catch (e: BuildError) {
            Response(data = null, code = e.code, msg = e.message ?: "操作失败")
        } catch (e: Exception) {
            val code = CODE_TASK_SEND_FAILED
            Response(
                data = null,
                code = code,
                msg = code.findMsg()
            )
        }
    }

    @Throws(BuildError::class)
    suspend inline fun <reified T> executedTask(
        task: BaseTask,
        crossinline buildFunc: ((ByteArray) -> T?)
    ): T? {
        val client = mClient
        if (client == null) {
            LogUtils.d("$TAG executedTask 未调用init方法初始化")
            val code = CODE_UNINITIALIZED
            throw BuildError(code, code.findMsg())
        }
        return suspendCancellableCoroutine { continuation ->
            task.callback = object : TaskSendListener {
                override fun onTaskSuccess(response: Any) {
                    val bytes = (response as? TaskProto)?.data?.toByteArray() ?: emptyArray
                    try {
                        continuation.resume(buildFunc.invoke(bytes))
                    } catch (e: Exception) {
                        LogUtils.e("$TAG request onTaskSuccess error $e")
                        val code = CODE_RESP_PARSE_FAILED
                        continuation.resumeWithException(BuildError(code, code.findMsg()))
                    }
                }

                override fun onTaskError(code: Int, msg: String) {
                    continuation.resumeWithException(BuildError(code, msg))
                }
            }
            //内部会切线程
            client.sendTask(task)
        }
    }

    private class TaskEncoder : MessageToByteEncoder<TaskProto>() {

        override fun encode(ctx: ChannelHandlerContext?, msg: TaskProto?, out: ByteBuf?) {
            if (msg == null || out == null) {
                return
            }
            //这里可以做个加密
            val data = msg.toByteArray()
            val length = data.size
            out.writeIntLE(length)
            out.writeBytes(data)
        }
    }


    private class TaskDecoder : LengthFieldBasedFrameDecoder(
        ByteOrder.LITTLE_ENDIAN,
        512 * 1024,
        0,
        Int.SIZE_BYTES,
        0,
        0,
        true
    ) {
        override fun decode(ctx: ChannelHandlerContext?, `in`: ByteBuf?): Any? {
            val result = (super.decode(ctx, `in`) as? ByteBuf)?.runCatching {
                //数据长度
                val length = readIntLE()
                LogUtils.d("$TAG 解码数据帧真实内容长度:$length")
                val content = readBytes(readableBytes())
                val bytes = ByteArray(content.readableBytes()).apply {
                    content.readBytes(this)
                }
                //如果数据有加密和密钥，在这里解密
                TaskProto.parseFrom(bytes)
            }
            return result?.getOrNull()
        }
    }

    class BuildError(val code: Int, msg: String) : IllegalArgumentException(msg)
}